package ibxm;
import java.io.*;
public class ProTracker {
        public static boolean is_mod( byte[] header_1084_bytes ) {
                boolean is_mod;
                is_mod = false;
                if( calculate_num_channels( header_1084_bytes ) > 0 ) {
                        is_mod = true;
                }
                return is_mod;
        }
        public static Module load_mod( byte[] header_1084_bytes, DataInput data_input ) throws IOException {
                int num_channels, channel_idx, panning;
                int sequence_length, restart_idx, sequence_idx;
                int num_patterns, pattern_idx, instrument_idx;
                Module module;
                num_channels = calculate_num_channels( header_1084_bytes );
                if( num_channels < 1 ) {
                        throw new IllegalArgumentException( "ProTracker: Unrecognised module format!" );
                }
                module = new Module();
                module.song_title = ascii_text( header_1084_bytes, 0, 20 );
                module.global_volume = 64;
                module.channel_gain = IBXM.FP_ONE * 3 / 8;
                module.default_speed = 6;
                module.default_tempo = 125;
                module.set_num_channels( num_channels );
                for( channel_idx = 0; channel_idx < num_channels; channel_idx++ ) {
                        panning = 64;
                        if( ( channel_idx & 0x03 ) == 0x01 || ( channel_idx & 0x03 ) == 0x02 ) {
                                panning = 192;
                        }
                        module.set_initial_panning( channel_idx, panning );
                }
                sequence_length = header_1084_bytes[ 950 ] & 0x7F;
                restart_idx = header_1084_bytes[ 951 ] & 0x7F;
                if( restart_idx >= sequence_length ) {
                        restart_idx = 0;
                }
                module.restart_sequence_index = restart_idx;
                module.set_sequence_length( sequence_length );
                for( sequence_idx = 0; sequence_idx < sequence_length; sequence_idx++ ) {
                        module.set_sequence( sequence_idx, header_1084_bytes[ 952 + sequence_idx ] & 0x7F );
                }
                num_patterns = calculate_num_patterns( header_1084_bytes );
                module.set_num_patterns( num_patterns );
                for( pattern_idx = 0; pattern_idx < num_patterns; pattern_idx++ ) {
                        module.set_pattern( pattern_idx, read_mod_pattern( data_input, num_channels ) );
                }
                module.set_num_instruments( 31 );
                for( instrument_idx = 1; instrument_idx <= 31; instrument_idx++ ) {
                        module.set_instrument( instrument_idx, read_mod_instrument( header_1084_bytes, instrument_idx, data_input ) );
                }
                return module;
        }
        private static int calculate_num_patterns( byte[] module_header ) {
                int num_patterns, order_entry, pattern_idx;
                num_patterns = 0;
                for( pattern_idx = 0; pattern_idx < 128; pattern_idx++ ) {
                        order_entry = module_header[ 952 + pattern_idx ] & 0x7F;
                        if( order_entry >= num_patterns ) {
                                num_patterns = order_entry + 1;
                        }
                }
                return num_patterns;
        }
       
        private static int calculate_num_channels( byte[] module_header ) {
                int num_channels;
                switch( ( module_header[ 1082 ] << 8 ) | module_header[ 1083 ] ) {
                        case 0x4b2e: /* M.K. */
                        case 0x4b21: /* M!K! */
                        case 0x542e: /* N.T. */
                        case 0x5434: /* FLT4 */
                                num_channels = 4;
                                break;
                        case 0x484e: /* xCHN */
                                num_channels = module_header[ 1080 ] - 48;
                                break;
                        case 0x4348: /* xxCH */
                                num_channels = ( ( module_header[ 1080 ] - 48 ) * 10 ) + ( module_header[ 1081 ] - 48 );
                                break;
                        default:
                                /* Not recognised. */
                                num_channels = 0;
                                break;
                }
                return num_channels;
        }
        private static Pattern read_mod_pattern( DataInput data_input, int num_channels ) throws IOException {
                int input_idx, output_idx;
                int period, instrument, effect, effect_param;
                Pattern pattern;
                byte[] input_pattern_data, output_pattern_data;
                pattern = new Pattern();
                pattern.num_rows = 64;
                input_pattern_data = new byte[ 64 * num_channels * 4 ];
                output_pattern_data = new byte[ 64 * num_channels * 5 ];
                data_input.readFully( input_pattern_data );
                input_idx = 0;
                output_idx = 0;
                while( input_idx < input_pattern_data.length ) {
                        period = ( input_pattern_data[ input_idx ] & 0x0F ) << 8;
                        period = period | ( input_pattern_data[ input_idx + 1 ] & 0xFF );
                        output_pattern_data[ output_idx ] = to_key( period );
                        instrument = input_pattern_data[ input_idx ] & 0x10;
                        instrument = instrument | ( ( input_pattern_data[ input_idx + 2 ] & 0xF0 ) >> 4 );
                        output_pattern_data[ output_idx + 1 ] = ( byte ) instrument;
                        effect = input_pattern_data[ input_idx + 2 ] & 0x0F;
                        effect_param = input_pattern_data[ input_idx + 3 ] & 0xFF;
                        if( effect == 0x08 && num_channels == 4 ) {
                                /* Some Amiga mods use effect 0x08 for reasons other than panning.*/
                                effect = 0;
                                effect_param = 0;
                        }
                        if( effect == 0x0A && effect_param == 0 ) {
                                /* Volume slide of zero has no effect.*/
                                effect = 0;
                        }
                        if( effect == 0x05 && effect_param == 0 ) {
                                /* Porta + Volume slide of zero has no effect.*/
                                effect = 0x03;
                        }
                        if( effect == 0x06 && effect_param == 0 ) {
                                /* Vibrato + Volume slide of zero has no effect.*/
                                effect = 0x04;
                        }
                        output_pattern_data[ output_idx + 3 ] = ( byte ) effect;
                        output_pattern_data[ output_idx + 4 ] = ( byte ) effect_param;
                        input_idx += 4;
                        output_idx += 5;
                }
                pattern.set_pattern_data( output_pattern_data );
                return pattern;
        }
        private static Instrument read_mod_instrument( byte[] mod_header, int idx, DataInput data_input ) throws IOException  {
                int header_offset, sample_data_length;
                int loop_start, loop_length, sample_idx;
                Instrument instrument;
                Sample sample;
                byte[] raw_sample_data;
                short[] sample_data;
                header_offset = ( idx - 1 ) * 30 + 20;
                instrument = new Instrument();
                instrument.name = ascii_text( mod_header, header_offset, 22 );
                sample = new Sample();
                sample.c2_rate = 8287;
                sample_data_length = unsigned_short_be( mod_header, header_offset + 22 ) << 1;
                sample.fine_tune = ( mod_header[ header_offset + 24 ] & 0x0F ) << 4;
                if( sample.fine_tune > 127 ) {
                        sample.fine_tune -= 256;
                }
                sample.volume = mod_header[ header_offset + 25 ] & 0x7F;
                loop_start = unsigned_short_be( mod_header, header_offset + 26 ) << 1;
                loop_length = unsigned_short_be( mod_header, header_offset + 28 ) << 1;
                if( loop_length < 4 ) {
                        loop_length = 0;
                }
                raw_sample_data = new byte[ sample_data_length ];
                sample_data = new short[ sample_data_length ];
                try {
                        data_input.readFully( raw_sample_data );
                } catch( EOFException e ) {
                        System.out.println( "ProTracker: Instrument " + idx + " has samples missing." );
                }
                for( sample_idx = 0; sample_idx < raw_sample_data.length; sample_idx++ ) {
                        sample_data[ sample_idx ] = ( short ) ( raw_sample_data[ sample_idx ] << 8 );
                }
                sample.set_sample_data( sample_data, loop_start, loop_length, false );
                instrument.set_num_samples( 1 );
                instrument.set_sample( 0, sample );
                return instrument;
        }
       
        private static byte to_key( int period ) {
                int oct, key;
                if( period < 32 ) {
                        key = 0;
                } else {
                        oct = LogTable.log_2( 7256 ) - LogTable.log_2( period );
                        if( oct < 0 ) {
                                key = 0;
                        } else {
                                key = oct * 12;
                                key = key >> ( IBXM.FP_SHIFT - 1 );
                                key = ( key >> 1 ) + ( key & 1 );
                        }
                }
                return ( byte ) key;
        }
        private static int unsigned_short_be( byte[] buf, int offset ) {
                int value;
                value = ( buf[ offset ] & 0xFF ) << 8;
                value = value | ( buf[ offset + 1 ] & 0xFF );
                return value;
        }
       
        private static String ascii_text( byte[] buffer, int offset, int length ) {
                int idx, chr;
                byte[] string_buffer;
                String string;
                string_buffer = new byte[ length ];
                for( idx = 0; idx < length; idx++ ) {
                        chr = buffer[ offset + idx ];
                        if( chr < 32 ) {
                                chr = 32;
                        }
                        string_buffer[ idx ] = ( byte ) chr;
                }
                try {
                        string = new String( string_buffer, 0, length, "ISO-8859-1" );
                } catch( UnsupportedEncodingException e ) {
                        string = "";
                }
                return string;
        }
}